{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "5163ad8d-faf3-4754-9d48-38672bc20afd",
   "metadata": {},
   "source": [
    "# NV-Ingest: Python Client Quick Start Guide\n",
    "\n",
    "This notebook provides a quick start guide to using the NV-Ingest Python API to create a client that interacts with a running NV-Ingest cluster. It will walk through the following:\n",
    "\n",
    "- Instantiate a client object\n",
    "- Define the task configuration for an NV-Ingest job\n",
    "- Submit a job the the NV-Ingest cluster\n",
    "- Retrieve completed results\n",
    "- Investigate the multimodal extractions\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6e0a6279-e78f-412a-94e7-a64ce5c0e4df",
   "metadata": {},
   "source": [
    "Specify a few parameters to connect to our nv-ingest cluster and a notional document to guide the examples."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "daf6a66e-1e8f-4d7c-ac70-91b2c79a9951",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "# client config\n",
    "HTTP_HOST = os.environ.get('HTTP_HOST', \"localhost\")\n",
    "HTTP_PORT = os.environ.get('HTTP_PORT', \"7670\")\n",
    "TASK_QUEUE = os.environ.get('TASK_QUEUE', \"morpheus_task_queue\")\n",
    "\n",
    "# minio config\n",
    "MINIO_ACCESS_KEY = os.environ.get('MINIO_ACCESS_KEY', \"minioadmin\")\n",
    "MINIO_SECRET_KEY = os.environ.get('MINIO_SECRET_KEY', \"minioadmin\")\n",
    "\n",
    "# time to wait for job to complete\n",
    "DEFAULT_JOB_TIMEOUT = 90\n",
    "\n",
    "# sample input file and output directory\n",
    "SAMPLE_PDF = \"/workspace/nv-ingest/data/multimodal_test.pdf\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "27bdcadb-678d-4316-8c11-0935e239b6b2",
   "metadata": {},
   "source": [
    "## The NV-Ingest Python Client"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5b93e648-ed90-45d6-bfc5-c2c4f83689b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "from base64 import b64decode\n",
    "import time\n",
    "\n",
    "from nv_ingest_client.client import NvIngestClient\n",
    "from nv_ingest_client.message_clients.rest.rest_client import RestClient\n",
    "from nv_ingest_client.primitives import JobSpec\n",
    "from nv_ingest_client.primitives.tasks import DedupTask\n",
    "from nv_ingest_client.primitives.tasks import EmbedTask\n",
    "from nv_ingest_client.primitives.tasks import ExtractTask\n",
    "from nv_ingest_client.primitives.tasks import FilterTask\n",
    "from nv_ingest_client.primitives.tasks import SplitTask\n",
    "from nv_ingest_client.primitives.tasks import StoreTask\n",
    "from nv_ingest_client.primitives.tasks import VdbUploadTask\n",
    "from nv_ingest_client.util.file_processing.extract import extract_file_content\n",
    "\n",
    "from IPython import display"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "17ee10bb-3abf-4caa-9dc2-950545f7300a",
   "metadata": {},
   "source": [
    "Load a sample PDF to demonstrate NV-Ingest usage."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "af2d095c-58d0-43a6-a6e5-d72a192a047c",
   "metadata": {},
   "outputs": [],
   "source": [
    "file_content, file_type = extract_file_content(SAMPLE_PDF)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e7e7e371-d452-4b7d-9679-97769c2339c9",
   "metadata": {},
   "source": [
    "Initialize a client that will submit jobs to our NV-Ingest cluster."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "65f66128-a82c-4b14-83ce-9622170dce25",
   "metadata": {},
   "outputs": [],
   "source": [
    "client = NvIngestClient(\n",
    "    message_client_allocator=RestClient,\n",
    "    message_client_hostname=HTTP_HOST,\n",
    "    message_client_port=HTTP_PORT,\n",
    "    message_client_kwargs=None,\n",
    "    msg_counter_id=\"nv-ingest-message-id\",\n",
    "    worker_pool_size=1,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67a8eeee-bac4-4cbd-b943-383f7b66e99c",
   "metadata": {},
   "source": [
    "A `JobSpec` is a specification for creating a job for submission to the NV-Ingest microservice. It accepts the following parameters:\n",
    "\n",
    "- `document_type` : The file type of the file to be ingested.\n",
    "- `payload` : A base64 encoded string of the file to be ingested.\n",
    "- `source_id` : An identifier that maps to the file, our example uses the filename here.\n",
    "- `source_name` : The name of the source for this ingest job, again, we use the filename.\n",
    "- `extented_options` : A dictionary of additional options to pass in to the ingest job, we pass in informations allowing us to conduct performance tracing of the job.\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c5a831f8-f5ed-4875-9ac1-b589c8e60a02",
   "metadata": {},
   "outputs": [],
   "source": [
    "job_spec = JobSpec(\n",
    "    document_type=file_type,\n",
    "    payload=file_content,\n",
    "    source_id=SAMPLE_PDF,\n",
    "    source_name=SAMPLE_PDF,\n",
    "    extended_options={\n",
    "        \"tracing_options\": {\n",
    "            \"trace\": True,\n",
    "            \"ts_send\": time.time_ns(),\n",
    "        }\n",
    "    },\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "12ae7b8c-06a6-4451-9131-350eeb011ecc",
   "metadata": {},
   "source": [
    "Each ingest job will include a set of tasks. These tasks will define the operations that will be performed during ingestion. This allows each job to potentially have different ingestion instructions. Here we define a simple extract oriented job, but the full list of supported options are contained below:\n",
    "\n",
    "- `ExtractTask` : Performs multimodal extractions from a document, including text, images, and tables.\n",
    "- `SplitTask` : Chunk the text into smaller chunks, useful for storing in a vector database for retrieval applications.\n",
    "- `Dedup` : Identifies duplicate images in document that can be filtered to remove data redundancy.\n",
    "- `Filter` : Filters out images that are likely not useful using some heuristics, including size and aspect ratio.\n",
    "- `EmbedTask` : Pass the text or table extractions through `\"nvidia/nv-embedqa-e5-v5` NIM to obtain its embeddings.\n",
    "- `Store` : Save the extracted tables or images to an S3 compliant object store like MinIO.\n",
    "- `Upload` : Save embeddings, chunks, and metadata to a Milvus vector database.\n",
    "\n",
    "After defining the ingestion tasks, they must be added to the job specification."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8032774-ffd7-4be4-8bd2-e94857808ab0",
   "metadata": {},
   "outputs": [],
   "source": [
    "extract_task = ExtractTask(\n",
    "    document_type=file_type,\n",
    "    extract_text=True,\n",
    "    extract_images=True,\n",
    "    extract_tables=True,\n",
    "    text_depth=\"document\",\n",
    "    extract_tables_method=\"yolox\",\n",
    ")\n",
    "\n",
    "dedup_task = DedupTask(\n",
    "    content_type=\"image\",\n",
    "    filter=True,\n",
    ")\n",
    "\n",
    "filter_task = FilterTask(\n",
    "    content_type=\"image\",\n",
    "    min_size=128,\n",
    "    max_aspect_ratio=5.0,\n",
    "    min_aspect_ratio=0.2,\n",
    "    filter=True,\n",
    ")\n",
    "\n",
    "job_spec.add_task(extract_task)\n",
    "job_spec.add_task(dedup_task)\n",
    "job_spec.add_task(filter_task)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a42b9d01-a58b-4a3d-8178-15bced9e4776",
   "metadata": {},
   "source": [
    "A job identifier is created for the job specification. This is used to retrieve the results upon completion.\n",
    "\n",
    "With the `job_id`, the job is submitted to the NV-Ingest microservice. When the job is complete, the results are fetched.\n",
    "\n",
    "Note, many jobs can be submitted and asynchronous execution is supported."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8dbddbb4-4fc8-4da0-914f-64d203b7a8f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "job_id = client.add_job(job_spec)\n",
    "client.submit_job(job_id, TASK_QUEUE)\n",
    "generated_metadata = client.fetch_job_result(\n",
    "    job_id, timeout=DEFAULT_JOB_TIMEOUT\n",
    ")[0][0]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "506bb426-0dff-4575-bc81-a1919d7bbbaf",
   "metadata": {},
   "source": [
    "## Explore the Outputs"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fcf56509-b9d8-4e38-a5fa-861fec3fbb0b",
   "metadata": {},
   "source": [
    "Let's explore elements of the NV-Ingest output. When data flows through an NV-Ingest pipeline, a number of extractions and transformations are performed. As the data is enriched, it is stored in rich metadata hierarchy. In the end, there will be a list of dictionaries, each of which represents a extracted type of information. The most common elements to extract from a dictionary in this hierarchy are the extracted content and the text representation of this content. The next few cells will demonstrate interacting with the metadata, pulling out these elements, and visualizing them. Note, when there is a `-1` value present, this represents non-applicable positional resolution. Positive numbers represent valid positional data.\n",
    "\n",
    "For a more complete description of metadata elements, view the data dictionary.\n",
    "\n",
    "[https://github.com/NVIDIA/nv-ingest/blob/main/docs/content-metadata.md](https://github.com/NVIDIA/nv-ingest/blob/main/docs/content-metadata.md)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "36ce5190-3119-4065-bce5-794861dc6220",
   "metadata": {},
   "outputs": [],
   "source": [
    "def redact_metadata_helper(metadata: dict) -> dict:\n",
    "    \"\"\"A simple helper function to redact `metadata[\"content\"]` so improve readability.\"\"\"\n",
    "    \n",
    "    text_metadata_redact = text_metadata.copy()\n",
    "    text_metadata_redact[\"content\"] = \"<---Redacted for readability--->\"\n",
    "    \n",
    "    return text_metadata_redact"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a2fce45c-c18d-48db-b9f6-82002690701d",
   "metadata": {},
   "source": [
    "### Explore Output - Text"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3813700a-7352-4792-aa08-eb39ea370009",
   "metadata": {},
   "source": [
    "This cell depicts the full metadata hierarchy for a text extraction with redacted content to ease readability. Notice the following sections are populated with information:\n",
    "\n",
    "- `content` - The raw extracted content, text in this case - this section will always be populated with a successful job.\n",
    "- `content_metadata` - Describes the type of extraction and its position in the broader document - this section will always be populated with a successful job.\n",
    "- `source_metadata` - Describes the source document that is the basis of the ingest job.\n",
    "- `text_metadata` - Contain information about the text extraction, including detected language, among others - this section will only exist when `metadata['content_metadata']['type'] == 'text'`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "976787fa-b832-47f6-8b48-56bf24ee1292",
   "metadata": {},
   "outputs": [],
   "source": [
    "text_metadata = generated_metadata[3][\"metadata\"]\n",
    "redact_metadata_helper(text_metadata)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e90316cb-cc77-4bed-b0ef-aa09dffab7ec",
   "metadata": {},
   "source": [
    "View the text extracted from the sample document."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0b5bdab3-6963-4056-a876-e19873d3f607",
   "metadata": {},
   "outputs": [],
   "source": [
    "text_metadata[\"content\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "14cc701e-e115-43d5-a1cd-f5d0f59cb007",
   "metadata": {},
   "source": [
    "### Explore Output - Tables"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "af410c0f-5b9b-43c1-9de9-bf1dbbed424f",
   "metadata": {},
   "source": [
    "This cell depicts the full metadata hierarchy for a table extraction with redacted content to ease readability. Notice the following sections are populated with information:\n",
    "\n",
    "- `content` - The raw extracted content, a base64 encoded image of the extracted table in this case - this section will always be populated with a successful job.\n",
    "- `content_metadata` - Describes the type of extraction and its position in the broader document - this section will always be populated with a successful job.\n",
    "- `source_metadata` - Describes the source and storage path of an extracted table in an S3 compliant object store.\n",
    "- `table_metadata` - Contains the text representation of the table, positional data, and other useful elements - this section will only exist when `metadata['content_metadata']['type'] == 'structured'`.\n",
    "\n",
    "Note, `table_metadata` will store chart and table extractions. The are distringuished by `metadata['content_metadata']['subtype']`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e6322d0-5af4-4459-95e8-444bc44d84c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "table_metadata = generated_metadata[4][\"metadata\"]\n",
    "redact_metadata_helper(table_metadata)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "303161ab-3e61-451f-860b-ce321f063b86",
   "metadata": {},
   "source": [
    "Visualize the table contained within the extracted metadata."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "facd7256-64a4-4a49-b48e-7d0e8fcc36c4",
   "metadata": {},
   "outputs": [],
   "source": [
    "display.Image(b64decode(table_metadata[\"content\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c851456a-f3ce-4ba4-b1d6-28a6446e8c6f",
   "metadata": {},
   "source": [
    "View the corresponding text that maps to this table. This text could be embedded to support multimodal retrieval workflows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "835417d3-598d-4e46-bea6-ab566a7e330a",
   "metadata": {},
   "outputs": [],
   "source": [
    "table_metadata[\"table_metadata\"][\"table_content\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b26833f4-88ed-46ff-9091-69d0f6fae3b4",
   "metadata": {},
   "source": [
    "### Explore Output - Charts"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "da6ca3d8-3e73-4dea-943c-bf5f4d0b91f3",
   "metadata": {},
   "source": [
    "This cell depicts the full metadata hierarchy for a chart extraction with redacted content to ease readability. Notice the following sections are populated with information:\n",
    "\n",
    "- `content` - The raw extracted content, a base64 encoded image of the extracted chart in this case - this section will always be populated with a successful job.\n",
    "- `content_metadata` - Describes the type of extraction and its position in the broader document - this section will always be populated with a successful job.\n",
    "- `source_metadata` - Describes the source and storage path of an extracted chart in an S3 compliant object store.\n",
    "- `table_metadata` - Contains the text representation of the chart, positional data, and other useful elements - this section will only exist when `metadata['content_metadata']['type'] == 'structured'`.\n",
    "\n",
    "Note, `table_metadata` will store chart and table extractions. The are distringuished by `metadata['content_metadata']['subtype']`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "228f44ba-9684-464d-b68c-1b7e7f8c454b",
   "metadata": {},
   "outputs": [],
   "source": [
    "chart_metadata = generated_metadata[5][\"metadata\"]\n",
    "chart_metadata_redact = chart_metadata.copy()\n",
    "chart_metadata_redact[\"content\"] = \"<---Redacted for readability--->\"\n",
    "chart_metadata_redact"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "74d2b96a-66c0-45d5-a381-c6af9f456ce7",
   "metadata": {},
   "source": [
    "Visualize the chart contained within the extracted metadata."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "940134fe-8edb-4cfa-b4c4-b121c3ab5447",
   "metadata": {},
   "outputs": [],
   "source": [
    "display.Image(b64decode(chart_metadata[\"content\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a9696008-3aaf-402e-ad46-408e5328d852",
   "metadata": {},
   "source": [
    "View the corresponding text that maps to this chart. This text could be embedded to support multimodal retrieval workflows."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "40ca47e0-ac5d-4105-b15e-3b4358569421",
   "metadata": {},
   "outputs": [],
   "source": [
    "chart_metadata[\"table_metadata\"][\"table_content\"]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c06c5e1b-8533-433f-8faf-375fd63b389e",
   "metadata": {},
   "source": [
    "### Explore Output - Images"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "bb6131ae-4e6d-4814-8a7b-e748d7462d72",
   "metadata": {},
   "source": [
    "This cell depicts the full metadata hierarchy for a image extraction with redacted content to ease readability. Notice the following sections are populated with information:\n",
    "\n",
    "- `content` - The raw extracted content, a base64 encoded image extracted from the document in this case - this section will always be populated with a successful job.\n",
    "- `content_metadata` - Describes the type of extraction and its position in the broader document - this section will always be populated with a successful job.\n",
    "- `source_metadata` - Describes the source and storage path of an extracted image in an S3 compliant object store.\n",
    "- `image_metadata` - Contains the image type, positional data, and other useful elements - this section will only exist when `metadata['content_metadata']['type'] == 'image'`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "426d4904-4620-4b59-ae0c-0d40916f111a",
   "metadata": {},
   "outputs": [],
   "source": [
    "img_metadata = generated_metadata[1][\"metadata\"]\n",
    "redact_metadata_helper(img_metadata)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d240e59-68a3-4262-ab22-1e83fffa8f51",
   "metadata": {},
   "source": [
    "Visualize the image contained within the extracted metadata."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6e10f383-d98c-4926-8274-d4c94147cf4c",
   "metadata": {},
   "outputs": [],
   "source": [
    "display.Image(b64decode(img_metadata[\"content\"]))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b7e70c61-3fbb-4999-af90-f67f9b861e12",
   "metadata": {},
   "source": [
    "### Optional:  Expanded Task Configuration"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cc594fb7-78a1-450e-808f-5e021733900e",
   "metadata": {},
   "source": [
    "This section illustrates usage of the remaining task types used when supporting retrieval workflows.\n",
    "\n",
    "- `StoreTask` - Stores extracted content to an S3 compliant object store (MinIO by default) and updates the `source_metadata` with the corresponding stored location.\n",
    "- `EmbedTask` - Computes an embedding for the extracted content using a [`nvidia/nv-embedqa-e5-v5`](https://catalog.ngc.nvidia.com/orgs/nim/teams/nvidia/containers/nv-embedqa-e5-v5) NVIDIA Inference Microservice (NIM) by default.\n",
    "- `VdbUploadTask` - Inserts ingested content into a Milvus vector database to support retrieval use cases."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d33795b0-7535-40af-b936-71c91276ddb4",
   "metadata": {},
   "source": [
    "Define the ingest job specification."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a333f588-55f5-4982-9404-b3ce593d8cd7",
   "metadata": {},
   "outputs": [],
   "source": [
    "job_spec = JobSpec(\n",
    "    document_type=file_type,\n",
    "    payload=file_content,\n",
    "    source_id=SAMPLE_PDF,\n",
    "    source_name=SAMPLE_PDF,\n",
    "    extended_options={\n",
    "        \"tracing_options\": {\n",
    "            \"trace\": True,\n",
    "            \"ts_send\": time.time_ns(),\n",
    "        }\n",
    "    },\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "71846b03-8548-429d-ad82-cf5c8d89cd0c",
   "metadata": {},
   "source": [
    "Here the task configuration is expanded, but requires the ancillary services (Embedding NIM, MinIO object stor, and Milvus Vector Database) to be up and running to return metadata back to the client."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4aa4eff5-02b1-43c0-b993-cb27e517abf8",
   "metadata": {},
   "outputs": [],
   "source": [
    "extract_task = ExtractTask(\n",
    "    document_type=file_type,\n",
    "    extract_text=True,\n",
    "    extract_images=True,\n",
    "    extract_tables=True,\n",
    "    text_depth=\"document\",\n",
    "    extract_tables_method=\"yolox\",\n",
    ")\n",
    "\n",
    "dedup_task = DedupTask(\n",
    "    content_type=\"image\",\n",
    "    filter=True,\n",
    ")\n",
    "\n",
    "filter_task = FilterTask(\n",
    "    content_type=\"image\",\n",
    "    min_size=128,\n",
    "    max_aspect_ratio=5.0,\n",
    "    min_aspect_ratio=0.2,\n",
    "    filter=True,\n",
    ")\n",
    "\n",
    "split_task = SplitTask(\n",
    "    split_by=\"word\",\n",
    "    split_length=300,\n",
    "    split_overlap=10,\n",
    "    max_character_length=5000,\n",
    "    sentence_window_size=0,\n",
    ")\n",
    "\n",
    "store_task = StoreTask(\n",
    "    structured=True,\n",
    "    images=True,\n",
    "    store_method=\"minio\",\n",
    "    extra_params={\n",
    "        \"access_key\": MINIO_ACCESS_KEY, \n",
    "        \"secret_key\": MINIO_SECRET_KEY,\n",
    "    }\n",
    ")\n",
    "\n",
    "embed_task = EmbedTask(\n",
    "    text=True,\n",
    "    tables=True,\n",
    ")\n",
    "\n",
    "vdb_upload_task = VdbUploadTask()\n",
    "\n",
    "job_spec.add_task(extract_task)\n",
    "job_spec.add_task(dedup_task)\n",
    "job_spec.add_task(filter_task)\n",
    "job_spec.add_task(store_task)\n",
    "job_spec.add_task(embed_task)\n",
    "job_spec.add_task(vdb_upload_task)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2ed98dbf-19d3-42ac-837a-5718dcd13e61",
   "metadata": {},
   "source": [
    "A job identifier is created for the job specification. This is used to retrieve the results upon completion.\n",
    "\n",
    "With the `job_id`, the job is submitted to the NV-Ingest cluster. When the job is complete, the results are fetched."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "997e3dab-ef0d-4580-ab3e-f77968c05513",
   "metadata": {},
   "outputs": [],
   "source": [
    "job_id = client.add_job(job_spec)\n",
    "client.submit_job(job_id, DEFAULT_TASK_QUEUE)\n",
    "generated_metadata = client.fetch_job_result(\n",
    "    job_id, timeout=DEFAULT_JOB_TIMEOUT\n",
    ")[0][0]"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.12"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
